package com.hzp.tool.mybatis;

import com.hzp.tool.common.ui.frame.BaseFrame;
import com.hzp.tool.common.ui.layout.LinedPanel;
import com.hzp.tool.common.util.UIUtils;
import com.hzp.tool.mybatis.custom.MyCommentGenerator;
import com.hzp.tool.mybatis.custom.MyGeneratePlugin;
import org.mybatis.generator.api.MyBatisGenerator;
import org.mybatis.generator.api.ShellCallback;
import org.mybatis.generator.config.Configuration;
import org.mybatis.generator.config.xml.ConfigurationParser;
import org.mybatis.generator.exception.ShellException;
import org.mybatis.generator.internal.DefaultShellCallback;
import org.mybatis.generator.internal.NullProgressCallback;

import javax.swing.*;
import javax.swing.border.TitledBorder;
import java.awt.*;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.CharArrayReader;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import static com.hzp.tool.common.util.UIUtils.*;

public class MybatisMain extends BaseFrame {

    private static final long serialVersionUID = -8729717584047326271L;

    private JTextField txt_connectionURL = new JTextField();
    private JTextField txt_userId = new JTextField();
    private JTextField txt_password = new JTextField();
    private JCheckBox box_comment = new JCheckBox("生成注释");
    private JCheckBox box_example = new JCheckBox("生成Example");
    private JCheckBox box_package = new JCheckBox("生成包路径");
    private JCheckBox box_serial = new JCheckBox("实现序列化");
    private JCheckBox box_lombok = new JCheckBox("使用Lombok");
    private JCheckBox box_swagger = new JCheckBox("使用Swagger");
    private JButton btn_advance = new JButton("显示选项");
    private JTextField txt_tablePrefix = new JTextField();
    private JTextField txt_modelSuffix = new JTextField();
    private JTextField txt_mapperSuffix = new JTextField();
    private JTextField txt_modelPackage = new JTextField();
    private JTextField txt_modelOutput = new JTextField();
    private JTextField txt_sqlMapPackage = new JTextField();
    private JTextField txt_sqlMapOutput = new JTextField();
    private JTextField txt_clientPackage = new JTextField();
    private JTextField txt_clientOutput = new JTextField();
    private JTextArea txt_tableNames = new JTextArea();
    private JTextArea txt_console = new JTextArea();
    private JButton btn_generateCode = new JButton("生成代码");

    public MybatisMain() {
        super("Mybatis代码生成工具", 1150, 600);
        initProperties();
    }

    @Override
    protected Component createHeader() {
        fixComponentsSize(400, 30, txt_connectionURL);
        fixComponentsSize(120, 30, txt_userId, txt_password);
        fixComponentsSize(250, 30, txt_modelPackage, txt_modelOutput, txt_clientPackage, txt_clientOutput, txt_sqlMapPackage, txt_sqlMapOutput);
        fixComponentsSize(100, 30, txt_tablePrefix, txt_modelSuffix, txt_mapperSuffix);

        txt_tableNames.setRows(6);
        btn_generateCode.setPreferredSize(new Dimension(76, 116));
        txt_clientPackage.addFocusListener(new FocusAdapter() {
            public void focusLost(FocusEvent e) {
                if (txt_sqlMapPackage.getText().length() == 0) {
                    txt_sqlMapPackage.setText(txt_clientPackage.getText());
                }
            }
        });
        btn_generateCode.addActionListener((e) -> generateCode());
        selectFileOnTextField(txt_modelOutput, "请选择Model 路径");
        selectFileOnTextField(txt_clientOutput, "请选择Mapper 路径");
        selectFileOnTextField(txt_sqlMapOutput, "请选择SqlMap 路径");
        LinedPanel head = new LinedPanel();
        head.appendLine(new Component[]{newLabel("数据库URL：", 100, 30), txt_connectionURL, newLabel("用户名：", 50, 30), txt_userId, newLabel("  密码：", 50, 30), txt_password}, new double[]{0, 1, 0, 0, 0, 0});
        head.appendLine(new Component[]{newLabel("Model 包名：", 100, 30), txt_modelPackage, newLabel("Model 路径：", 100, 30), txt_modelOutput, newLabel("Model后缀：", 80, 30), txt_modelSuffix}, new double[]{0, 0.5, 0, 0.5, 0, 0});
        head.appendLine(new Component[]{newLabel("Mapper 包名：", 100, 30), txt_clientPackage, newLabel("Mapper 路径：", 100, 30), txt_clientOutput, newLabel("Mapper后缀：", 80, 30), txt_mapperSuffix}, new double[]{0, 0.5, 0, 0.5, 0, 0});
        head.appendLine(new Component[]{newLabel("SqlMap 包名：", 100, 30), txt_sqlMapPackage, newLabel("SqlMap 路径：", 100, 30), txt_sqlMapOutput, newLabel("删除表前缀：", 80, 30), txt_tablePrefix}, new double[]{0, 0.5, 0, 0.5, 0, 0});
        head.appendLine(new Component[]{newLabel("自定义选项：", 100, 30), createOption()}, new double[]{0, 1});
        head.appendLine(new Component[]{newLabel("表名称列表：", 100, 30), wrapWithScroll(txt_tableNames), btn_generateCode}, new double[]{0, 1, 0});
        head.setBorder(new TitledBorder(""));
        return head;
    }

    @Override
    protected Component createContent() {
        JScrollPane panel = wrapWithScroll(txt_console);
        panel.setBorder(new TitledBorder(""));
        return panel;
    }

    private Component createOption() {

        fixComponentsSize(100, 30, box_package, box_comment, box_example, box_serial, box_lombok, box_swagger);

        LinedPanel base = new LinedPanel();
        base.appendLine(new Component[]{box_package, box_comment, box_example, box_serial, box_lombok, box_swagger, newLabel("")}, new double[]{0, 0, 0, 0, 0, 0, 1});
        LinedPanel advance = new LinedPanel();
        // advance.appendLine(new Component[]{}, new double[]{0, 0, 1});
        JPanel panel = new JPanel(new BorderLayout());
        panel.add(base, BorderLayout.NORTH);

        btn_advance.addActionListener(e -> {
            String cmd = e.getActionCommand();
            if ("true".equals(cmd)) {
                cmd = "false";
                panel.remove(advance);
                btn_advance.setText("显示选项");
            } else {
                cmd = "true";
                panel.add(advance, BorderLayout.CENTER);
                btn_advance.setText("隐藏选项");
            }
            btn_advance.setActionCommand(cmd);
            refresh();
        });
        return panel;
    }

    private void initProperties() {
        addPropertyComponent("jdbc_url", txt_connectionURL);
        addPropertyComponent("user_id", txt_userId);
        addPropertyComponent("password", txt_password);
        addPropertyComponent("model_package", txt_modelPackage);
        addPropertyComponent("model_output", txt_modelOutput);
        addPropertyComponent("client_package", txt_clientPackage);
        addPropertyComponent("client_output", txt_clientOutput);
        addPropertyComponent("sqlmap_package", txt_sqlMapPackage);
        addPropertyComponent("sqlmap_output", txt_sqlMapOutput);
        addPropertyComponent("enable_package", box_package, "false");
        addPropertyComponent("enable_comment", box_comment, "false");
        addPropertyComponent("enable_example", box_example, "false");
        addPropertyComponent("table_prefix", txt_tablePrefix);
        addPropertyComponent("model_suffix", txt_modelSuffix);
        addPropertyComponent("mapper_suffix", txt_mapperSuffix);
        addPropertyComponent("enable_serial", box_serial);
        addPropertyComponent("enable_lombok", box_lombok);
        addPropertyComponent("enable_swagger", box_swagger);
        addPropertyComponent("table_names", txt_tableNames);
    }

    private void selectFileOnTextField(JTextField field, String title) {
        field.addMouseListener(new MouseAdapter() {
            public void mouseClicked(MouseEvent e) {
                if (e.getClickCount() == 2) {
                    File file = UIUtils.selectFolder(MybatisMain.this, title, field.getText().trim());
                    if (file != null) {
                        field.setText(file.getAbsolutePath());
                    }
                }
            }
        });
    }

    private void generateCode() {
        StringBuilder xml = new StringBuilder();
        try {
            String jdbcUrl = getTextFrom(txt_connectionURL, "数据库URL不能为空！");
            String driverClass;
            if (jdbcUrl.startsWith("jdbc:mysql")) {
                driverClass = "com.mysql.jdbc.Driver";
            } else if (jdbcUrl.startsWith("jdbc:oracle")) {
                driverClass = "oracle.jdbc.driver.OracleDriver";
            } else if (jdbcUrl.startsWith("jdbc:sqlserver")) {
                driverClass = "com.microsoft.sqlserver.jdbc.SQLServerDriver";
            } else if (jdbcUrl.startsWith("jdbc:db2")) {
                driverClass = "com.ibm.db2.jcc.DB2Driver";
            } else {
                driverClass = prompt("不支持自动获取JDBC驱动类型，请手动输入JDBC Driver Class：");
                if (driverClass == null || driverClass.trim().length() == 0) {
                    throw new Exception("请输入JDBC Driver Class");
                }
            }
            String userName = getTextFrom(txt_userId, "用户名不能为空！");
            String password = getTextFrom(txt_password, "请输入密码！");
            String modelPackage = getTextFrom(txt_modelPackage, "请输入Model 包名！");
            String modelOutput = getTextFrom(txt_modelOutput, "请输入Model 路径！");
            String clientPackage = getTextFrom(txt_clientPackage, "请输入Mapper 包名！");
            String clientOutput = getTextFrom(txt_clientOutput, "请输入Mapper 路径！");
            String sqlMapPackage = getTextFrom(txt_sqlMapPackage, "请输入SqlMap 包名！");
            String sqlMapOutput = getTextFrom(txt_sqlMapOutput, "请输入SqlMap 路径！");
            String[] tableNames = getTextFrom(txt_tableNames, "请输入表名称列表！").split("\\s*[\\n;,]\\s*");
            String tablePrefix = getTextFrom(txt_tablePrefix, "").toLowerCase();
            MyGeneratePlugin.setModelSuffix(getTextFrom(txt_modelSuffix, ""));
            MyGeneratePlugin.setMapperSuffix(getTextFrom(txt_mapperSuffix, ""));
            MyGeneratePlugin.setEnableSerial(box_serial.isSelected());
            MyGeneratePlugin.setEnableLombok(box_lombok.isSelected());
            MyGeneratePlugin.setEnableSwagger(box_swagger.isSelected());
            boolean enableExample = box_example.isSelected(), enableComment = box_comment.isSelected();
            xml.append("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n");
            xml.append("<!DOCTYPE generatorConfiguration PUBLIC \"-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN\" \"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd\" >\n");
            xml.append("<generatorConfiguration>\n");
            xml.append("	<context id=\"context1\">\n");
            xml.append("		<property name=\"javaFileEncoding\" value=\"UTF-8\" />\n");
            // 插件
            xml.append("		<plugin type=\"" + MyGeneratePlugin.class.getName() + "\" />\n");
            // 注释
            xml.append("		<commentGenerator type=\"" + MyCommentGenerator.class.getName() + "\">\n");
            xml.append("			<property name=\"suppressAllComments\" value=\"" + !enableComment + "\" />\n");
            xml.append("		</commentGenerator>\n");
            // 数据库配置
            xml.append("		<jdbcConnection driverClass=\"" + driverClass + "\" connectionURL=\"" + escape(jdbcUrl) + "\" userId=\"" + escape(userName) + "\" password=\"" + escape(password) + "\" />\n");
            // Model
            xml.append("		<javaModelGenerator targetPackage=\"" + modelPackage + "\" targetProject=\"" + modelOutput + "\">\n");
            xml.append("		    <property name=\"enableSubPackages\" value=\"false\"/>\n");
            xml.append("		</javaModelGenerator>\n");
            // SqlMap
            xml.append("		<sqlMapGenerator targetPackage=\"" + sqlMapPackage + "\" targetProject=\"" + sqlMapOutput + "\">\n");
            xml.append("		    <property name=\"enableSubPackages\" value=\"false\"/>\n");
            xml.append("		</sqlMapGenerator>\n");
            // Mapper
            xml.append("		<javaClientGenerator targetPackage=\"" + clientPackage + "\" targetProject=\"" + clientOutput + "\"  type=\"XMLMAPPER\" >\n");
            xml.append("		    <property name=\"enableSubPackages\" value=\"false\"/>\n");
            xml.append("		</javaClientGenerator>\n");
            for (String tableName : tableNames) {
                if (tableName.length() == 0) {
                    continue;
                }
                String domainName = tableName;
                if (tablePrefix.length() > 0 && domainName.toLowerCase().startsWith(tablePrefix)) {
                    domainName = domainName.substring(tablePrefix.length());
                    if (domainName.startsWith("_")) {
                        domainName = domainName.substring(1);
                    }
                }
                domainName = toCamel(domainName);
                xml.append("		<table tableName=\"" + tableName + "\" domainObjectName=\"" + domainName + "\"");
                if (enableExample) {
                    xml.append("/>\n");
                } else {
                    xml.append("	        enableCountByExample=\"false\" enableSelectByExample=\"false\" selectByExampleQueryId=\"false\"");
                    xml.append("	        enableUpdateByExample=\"false\" enableDeleteByExample=\"false\" />\n");
                }
            }
            xml.append("	</context>\n");
            xml.append("</generatorConfiguration>\n");
        } catch (Exception e) {
            alert(e.getMessage());
            return;
        }
        try {
            doGenerateCode(xml.toString());
        } catch (Exception e) {
            e.printStackTrace();
            System.err.println(xml.toString());
            alert("生成代码出现异常：" + e.getMessage());
            return;
        }
    }

    private String escape(String str) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < str.length(); i++) {
            char c = str.charAt(i);
            if (c == '&') {
                sb.append("&amp;");
            } else if (c == '<') {
                sb.append("&lt;");
            } else if (c == '>') {
                sb.append("&gt;");
            } else if (c == '"') {
                sb.append("&quot;");
            } else {
                sb.append(c);
            }
        }
        return sb.toString();
    }

    private String toCamel(String name) {
        String[] values = name.split("_");
        StringBuilder builder = new StringBuilder();
        for (String value : values) {
            value = value.trim();
            if (value.length() == 0) continue;
            char[] chars = value.toCharArray();
            for (int i = 0; i < chars.length; i++) {
                char c = i == 0 ? Character.toUpperCase(chars[i]) : Character.toLowerCase(chars[i]);
                builder.append(c);
            }
        }
        return builder.toString();
    }

    private void doGenerateCode(String configXml) throws Exception {
        txt_console.setText("");
        PrintStream srcOut = System.out, srcErr = System.err;
        System.setOut(consoleStream);
        System.setErr(consoleStream);
        try {
            List<String> warnings = new ArrayList<>();
            ConfigurationParser parser = new ConfigurationParser(warnings);
            Configuration conf = parser.parseConfiguration(new CharArrayReader(configXml.toCharArray()));
            Map<File, File> directoryMapping = new HashMap<>();
            ShellCallback callback = new DefaultShellCallback(true) {
                public File getDirectory(String targetProject, String targetPackage) throws ShellException {
                    File project = new File(targetProject);
                    if (!project.exists()) project.mkdirs();
                    if (!project.isDirectory()) throw new ShellException("输出路径无效：" + targetProject);
                    File directory = project;
                    if (box_package.isSelected()) {
                        directory = new File(project, targetPackage.replace('.', File.separatorChar));
                        if (!directory.isDirectory() && !directory.mkdirs()) throw new ShellException("创建文件目录失败：" + directory);
                    }
                    File temp = new File(directory, UUID.randomUUID().toString().replace("-", ""));
                    temp.mkdirs();
                    directoryMapping.put(temp, directory);
                    return temp;
                }
            };
            new MyBatisGenerator(conf, callback, warnings).generate(new NullProgressCallback() {
                int confirmState = -1;

                int confirmExistFile(String file) {
                    if (confirmState != -1) return confirmState;
                    int state = JOptionPane.showOptionDialog(getBaseFrame(), file + "\n文件已经存在，请选择处理方式：", "文件存在提示", JOptionPane.YES_OPTION, JOptionPane.WARNING_MESSAGE, null, new String[]{"覆盖此文件", "覆盖所有文件", "创建新文件", "跳过此文件"}, "覆盖文件");
                    if (state == 1) {
                        confirmState = state = 0;
                    }
                    return state;
                }

                @Override
                public void done() {
                    directoryMapping.forEach((key, value) -> {
                        for (File file : key.listFiles()) {
                            File dest = new File(value, file.getName());
                            if (dest.exists()) {
                                int state = confirmExistFile(dest.getAbsolutePath());
                                if (state == 0) {
                                    dest.delete();
                                    file.renameTo(dest);
                                } else if (state == 2) {
                                    dest = new File(value, file.getName() + ".new");
                                    if (dest.exists()) dest.delete();
                                    file.renameTo(dest);
                                } else {
                                    file.delete();
                                }
                            } else {
                                file.renameTo(dest);
                            }
                        }
                        key.delete();
                    });
                }
            });
            if (warnings.size() > 0) {
                warnings.forEach(msg -> System.out.println(msg));
            }
            System.out.println("生成完成.");
        } finally {
            consoleStream.flush();
            System.setOut(srcOut);
            System.setErr(srcErr);
        }
    }

    private PrintStream consoleStream = new PrintStream(new OutputStream() {
        byte[] buf = new byte[1024];
        int length = 0;

        public void write(int b) throws IOException {
            if (length >= 1024) {
                flush();
            }
            buf[length++] = (byte) b;
        }

        public void flush() throws IOException {
            String str = new String(buf, 0, length);
            length = 0;
            txt_console.append(str);
        }

        public void close() throws IOException {
            flush();
        }
    });

    public static void main(String[] args) {
        new MybatisMain().launch();
    }
}
